﻿//************************************************************************************************************
// CoNatural.Threading.dll
// Author: Roger Torres
//************************************************************************************************************
// You can use, modify, and distribute the source code and executable programs based on the source code.
// This source code is provided "as is" and without warranties as to performance or merchantability. 
// The author and/or distributors of this source code may have made statements about this source code. 
// Any such statements do not constitute warranties and shall not be relied on by the user in deciding 
// whether to use this source code. This source code is provided without any express or implied warranties 
// whatsoever. Because of the diversity of conditions and hardware under which this source code may be used, 
// no warranty of fitness for a particular purpose is offered. The user is advised to test the source code 
// thoroughly before relying on it. The user must assume the entire risk of using the source code.
//************************************************************************************************************

using System;
using System.Collections.Generic;

namespace CoNatural.Threading.Async {
   /// <summary>
   /// Wraps a sequence of async actions to be executed in order.
   /// </summary>
   public class Serial : AsyncAction {
      private IEnumerable<AsyncAction> _actions;

      /// <summary>
      /// Constructor.
      /// </summary>
      /// <param name="name">The name of the action.</param>
      /// <param name="raiseEvents">True to raise events, False otherwise.</param>
      /// <param name="actions">The sequence of internal actions.</param>
      public Serial(string name, bool raiseEvents, IEnumerable<AsyncAction> actions)
         : base(name, raiseEvents) {
         _actions = actions;
      }

      /// <summary>
      /// Starts recursive iterator by calling MoveNext() with the first action in the sequence.
      /// </summary>
      /// <param name="source">The source async action.</param>
      /// <param name="nextAction">The delegate to continue.</param>
      internal override void BeginInvoke(AsyncAction source, Action nextAction) {
         Context = source.Context;
         MoveNext(_actions.GetEnumerator(), nextAction);
      }

      /// <summary>
      /// The internal action's enumerator is advanced, and each action is invoked
      /// with a recursive delegate to MoveNext() to continue the sequence.
      /// The sequence is completed if a cancellation request is found in the execution context.
      /// Exceptions will also complete the action and resume the operation by invoking the next action's delegate.
      /// </summary>
      /// <param name="actionEnumerator">The internal sequence enumerator.</param>
      /// <param name="nextAction">The delegate to continue.</param>
      private void MoveNext(IEnumerator<AsyncAction> actionEnumerator, Action nextAction) {
         try {
            // stop when cancel requested in context
            if (Context.CancelRequested) {
               Complete(true, null);
               nextAction();
            }
            // advance iteration
            else if (actionEnumerator.MoveNext()) {
               actionEnumerator.Current.BeginInvoke(this, () => MoveNext(actionEnumerator, nextAction));
            }
            // we are done
            else {
               Complete(false, null);
               nextAction();
            }
         }
         catch (AsyncActionException aex) {
            Complete(false, aex);
            nextAction();
         }
         catch (Exception ex) {
            Complete(false, new AsyncActionException(this, string.Empty, ex));
            nextAction();
         }
      }
   }

   /// <summary>
   /// Special serial async action that yields a result of type T on completion.
   /// Works in concert with Return actions.
   /// </summary>
   /// <typeparam name="T">The type of the result.</typeparam>
   public class Serial<T> : Serial {
      /// <summary>
      /// The result of the serial action.
      /// </summary>
      public T Result { get; internal set; }

      /// <summary>
      /// Constructor.
      /// </summary>
      /// <param name="name">The name of the action.</param>
      /// <param name="raiseEvents">True to raise events, False otherwise.</param>
      /// <param name="actions">The sequence of internal actions.</param>
      public Serial(string name, bool raiseEvents, IEnumerable<AsyncAction> actions)
         : base(name, raiseEvents, actions) { }
   }
}
